//
//  Vec.h
//  Cartoonization
//
//  Created by Adrien MATRICON on 04/03/13.
//  Copyright (c) 2013 Adrien MATRICON. All rights reserved.
//

#ifndef Cartoonization_Vec_h
#define Cartoonization_Vec_h

#include <math.h>
#include <iostream>

struct Vec{
	double x,y,z;
	inline Vec (double a_x, double a_y, double a_z):x(a_x), y(a_y), z(a_z) {}
	inline Vec () : x(0), y(0), z(0) {}
    inline Vec (const Vec & v):x(v.x), y(v.y), z(v.z) {}
    inline Vec (double* tab): x(tab[0]), y(tab[1]), z(tab[2]) {}
	
	inline double& operator[] (int id)
	{
		if(id == 0)
		{
			return(x);
		}
		
		if(id == 1)
		{
			return(y);
		}
		
        return (z);
    };
	
    inline const double operator[] (int id) const
	{
        if(id == 0)
		{
			return(x);
		}
		
		if(id == 1)
		{
			return(y);
		}
		
        return (z);
    };
	
    inline Vec& operator= (const Vec & v)
	{
        x = v.x;
        y = v.y;
		z = v.z;
        return (*this);
    };
	
    inline Vec& operator+= (const Vec & v)
	{
        x += v.x;
        y += v.y;
		z += v.z;
        return (*this);
    };
	
    inline Vec& operator-= (const Vec & v)
	{
        x -= v.x;
        y -= v.y;
		z -= v.z;
        return (*this);
    };
	
    inline Vec& operator^= (const Vec & v)
	{
        Vec tmp;
		
		tmp.x = (y*v.z)-(z*v.y);
		tmp.y = (z*v.x)-(x*v.z);
		tmp.z = (x*v.y)-(y*v.x);
		
		x = tmp.x;
        y = tmp.y;
		z = tmp.z;
        return (*this);
    };
	
    inline Vec& operator*= (double s)
	{
        x *= s;
        y *= s;
        z *= s;
        return (*this);
    };
	
    inline Vec& operator/= (double s)
	{
        x /= s;
        y /= s;
        z /= s;
        return (*this);
    };
	
    inline double squaredLength() const
	{
        return ( (x*x) + (y*y) + (z*z) );
    };
	
    inline double length() const
	{
        return (double)sqrt (squaredLength());
    };
	
    inline double normalize ()
	{
        double l = length();
        if (l == 0.0f)
            return 0;
        double inverse = 1.0f / l;
        x *= inverse;
        y *= inverse;
        z *= inverse;
        return l;
    };
	
    inline double distanceTo (const Vec &v)
	{
        Vec tmp(*this);
		tmp -= v;
		return ( tmp.length());
    };
	
	inline static double det(const Vec &v0, const Vec &v1, const Vec &v2)
	{
		Vec tmp(v0);
		tmp ^= v1;
		return ( (tmp.x * v2.x) + (tmp.y * v2.y) + (tmp.z * v2.z) );
	};
};

inline bool operator!= (const Vec & p1, const Vec & p2) {
	return (p1.x != p2.x || p1.y != p2.y || p1.z != p2.z);
}

inline Vec operator* (const Vec & p, double factor) {
	return Vec (p.x * factor, p.y * factor, p.z * factor);
}

inline Vec operator* (double factor, const Vec & p) {
	return Vec (p.x * factor, p.y * factor, p.z * factor);
}

inline double operator* (const Vec & p1, const Vec & p2) {
	return (p1.x * p2.x) + (p1.y * p2.y) + (p1.z * p2.z);
}

inline Vec operator^ (const Vec & p1, const Vec & p2) {
	return Vec ( (p1.y*p2.z)-(p1.z*p2.y), (p1.z*p2.x)-(p1.x*p2.z), (p1.x*p2.y)-(p1.y*p2.x));
}

inline Vec operator+ (const Vec & p1, const Vec & p2) {
	return Vec (p1.x + p2.x, p1.y + p2.y, p1.z + p2.z);
}

inline Vec operator- (const Vec & p1, const Vec & p2) {
	return Vec (p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
}

inline Vec operator- (const Vec & p) {
	return Vec (-p.x, -p.y, -p.z);
}

inline Vec operator/ (const Vec & p, double divisor) {
	return Vec (p.x/divisor, p.y/divisor, p.z/divisor);
}

inline bool operator== (const Vec & p1, const Vec & p2) {
	return (p1[0] == p2.x && p1.y == p2.y && p1.z == p2.z);
}

inline bool operator< (const Vec & a, const Vec & b) {
	return (a.x < b.x && a.y < b.y && a.z < b.z);
}

inline bool operator<= (const Vec & a, const Vec & b) {
	return (a.x <= b.x || a.y <= b.y || a.z <= b.z);
}

inline bool operator> (const Vec & a, const Vec & b) {
	return (a.x > b.x && a.y > b.y && a.z > b.z);
}

inline bool operator>= (const Vec & a, const Vec & b) {
	return (a.x >= b.x || a.y >= b.y || a.z >= b.z);
}

inline Vec rotate(Vec toRotate, Vec n_before, Vec n_after)
{
	Vec n1 = n_before, n2 = n_after;
	
	n1.normalize();
	n2.normalize();
	
	Vec axis = n1 ^ n2;
	
	if(axis.normalize() == 0)
	{
		if(n_before * n_after > 0)
		{
			return(toRotate);
		}
		return(-toRotate);
	}
	
	double cosinus = n1 * n2;
	double sinus = n2 * (axis ^ n1);
	
	Vec result = (axis*toRotate) * axis;
	
	result += cosinus * (toRotate - result) + sinus * (axis ^ (toRotate - result));
	
	return(result);
};

#endif
